package baseFx.common.utils;

import baseFx.common.Action1T;
import baseFx.common.Env;
import baseFx.common.Func1T;
import baseFx.common.Stream;

import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.net.JarURLConnection;
import java.net.URI;
import java.net.URL;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class ClassUtils {
    public static List<Class<?>> parents(Class<?> clazz) {
        List<Class<?>> list = new ArrayList<>();
        Class<?> p = clazz.getSuperclass();
        while (p != null && p != Object.class) {
            list.add(p);
            p = p.getSuperclass();
        }
        return list;
    }

    public static List<Class<?>> interfaces(Class<?> clazz) {
        List<Class<?>> list = new ArrayList<>(Arrays.asList(clazz.getInterfaces()));
        for (Class<?> i : clazz.getInterfaces()) {
            list.addAll(interfaces(i));
        }
        List<Class<?>> ps = parents(clazz);
        for (Class<?> p : ps) {
            list.addAll(interfaces(p));
        }
        return Stream.create(list).distinct().toList();
    }

    public static List<Field> fields(Class<?> clazz, Func1T<Field, Boolean> func) {
        List<Field> fields = new ArrayList<>();
        while (clazz != Object.class) {
            Field[] fs = clazz.getDeclaredFields();
            for (Field f : fs) {
                if (func != null && func.execute(f) == false) {
                    continue;
                }
                if (f.isAccessible() == false) {
                    f.setAccessible(true);
                }
                fields.add(f);
            }
            clazz = clazz.getSuperclass();
        }
        return fields;
    }

    public static Field field(Class<?> clazz, String name) {
        while (clazz != Object.class) {
            try {
                Field f = clazz.getDeclaredField(name);
                if (f != null) {
                    if (f.isAccessible() == false) {
                        f.setAccessible(true);
                    }
                    return f;
                }
            } catch (NoSuchFieldException ignored) {

            }
            clazz = clazz.getSuperclass();
        }
        return null;
    }

    public static <T> T newInstance(Class<T> clazz) {
        if (clazz == null) {
            return null;
        }
        try {
            if (clazz.getTypeName().contains("$") || Modifier.isPublic(clazz.getModifiers()) == false) {
                if (Modifier.isStatic(clazz.getModifiers()) == false && clazz.getTypeName().contains("$")) {
                    throw new RuntimeException("内部类必须是静态的");
                }
                Constructor<T> con = clazz.getDeclaredConstructor();
                if (con.isAccessible() == false) {
                    con.setAccessible(true);
                }
                return con.newInstance();
            }
            return clazz.newInstance();
        } catch (Exception e) {
            throw ExceptionUtils.toRuntime(e);
        }
    }

    public static <T> T newInstance(Class<T> clazz, Action1T<T> action) {
        T model = newInstance(clazz);
        if (model != null && action != null) {
            action.execute(model);
        }
        return model;
    }

    public static List<Field> fields(Class<?> clazz) {
        return fields(clazz, null);
    }

    public static Method getMethod(Class<?> clazz, String name) {
        try {
            while (clazz != Object.class) {
                Method[] ms = clazz.getMethods();
                for (Method m : ms) {
                    if (m.getName().equals(name)) {
                        return m;
                    }
                }
                clazz = clazz.getSuperclass();
            }
            return null;
        } catch (Exception e) {
            throw ExceptionUtils.toRuntime(e);
        }
    }

    public static List<Method> getMethods(Class<?> clazz, String name) {
        try {
            List<Method> list = new ArrayList<>();
            while (clazz != Object.class) {
                Method[] ms = clazz.getMethods();
                for (Method m : ms) {
                    if (m.getName().equals(name)) {
                        list.add(m);
                    }
                }
                clazz = clazz.getSuperclass();
            }
            return list;
        } catch (Exception e) {
            throw ExceptionUtils.toRuntime(e);
        }
    }

    public static List<Method> getMethods(Class<?> clazz) {
        try {
            List<Method> list = new ArrayList<>();
            while (clazz != Object.class) {
                list.addAll(Arrays.asList(clazz.getMethods()));
                clazz = clazz.getSuperclass();
            }
            return list;
        } catch (Exception e) {
            throw ExceptionUtils.toRuntime(e);
        }
    }

    public static Class<?> getClassForName(String name) {
        try {
            return Thread.currentThread().getContextClassLoader().loadClass(name);
        } catch (ClassNotFoundException e) {
            throw ExceptionUtils.toRuntime(e);
        }
    }

    public static boolean isAnnotationPresent(AnnotatedElement element, Class<?> aClazz) {
        List<Class<?>> list = new ArrayList<>();
        for (Annotation item : element.getAnnotations()) {
            if (item.annotationType() == aClazz) {
                return true;
            }
            if (item.annotationType() == element) {
                return false;
            }
            if (isAnnotationPresent(list, item.annotationType(), aClazz)) {
                return true;
            }
        }
        return false;
    }

    private static boolean isAnnotationPresent(List<Class<?>> stack, AnnotatedElement element, Class<?> aClazz) {
        for (Annotation item : element.getAnnotations()) {
            if (item.annotationType() == aClazz) {
                return true;
            }
            int idx = stack.indexOf(item.annotationType());
            if (idx >= 0) {
                continue;
            }
            stack.add(item.annotationType());
            if (isAnnotationPresent(stack, item.annotationType(), aClazz)) {
                return true;
            }
        }
        return false;
    }

    public static Object getValue(Field field, Object instance) {
        try {
            return field.get(instance);
        } catch (IllegalAccessException e) {
            throw ExceptionUtils.toRuntime(e);
        }
    }

    public static void setValue(Field field, Object instance, Object value) {
        try {
            field.set(instance, value);
        } catch (IllegalAccessException e) {
            throw ExceptionUtils.toRuntime(e);
        }
    }

    public static Object invoke(Object instance, String methodName, Object... args) {
        try {
            Method m = getMethod(instance.getClass(), methodName);
            return m.invoke(instance, args);
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw ExceptionUtils.toRuntime(e);
        }
    }

    public static List<Class<?>> getClassByPackage(String packageName, boolean includeSubpack) {
        ClassLoader classLoader;
        if (null == Env.getMainClass()) {
            classLoader = ClassLoader.getSystemClassLoader();
        } else {
            classLoader = Env.getMainClass().getClassLoader();
        }
        URL url = classLoader.getResource(packageName.replace(".", "/"));
        if (url == null) {
            return new ArrayList<>();
        }
        String protocol = url.getProtocol();
        List<Class<?>> list = new ArrayList<>();
        if ("file".equals(protocol)) {
            list.addAll(findClassLocal(classLoader, packageName, includeSubpack));
        } else if ("jar".equals(protocol)) {
            list.addAll(findClassJar(classLoader, packageName, includeSubpack));
        }
        return Stream.create(list).distinct(i -> i.getTypeName()).toList();
    }

    private static List<Class<?>> findClassLocal(ClassLoader classLoader, String packageName, boolean includeSubpack) {
        try {
            List<Class<?>> rslist = new ArrayList<>();
            URI url = classLoader.getResource(packageName.replace(".", "/")).toURI();
            File file = new File(url);
            file.listFiles(chiFile -> {
                if (chiFile.isDirectory() && includeSubpack) {
                    List<Class<?>> list = findClassLocal(classLoader, packageName + "." + chiFile.getName(), true);
                    rslist.addAll(list);
                }
                if (chiFile.getName().endsWith(".class")) {
                    try {
                        Class<?> clazz = classLoader.loadClass(packageName + "." + chiFile.getName().replace(".class", ""));
                        rslist.add(clazz);
                    } catch (ClassNotFoundException e) {
                        //..
                    }
                    return true;
                }
                return false;
            });
            return rslist;
        } catch (Exception e) {
            throw ExceptionUtils.toRuntime(e);
        }
    }

    private static List<Class<?>> findClassJar(ClassLoader classLoader, String packName, boolean includeSubpack) {
        try {
            List<Class<?>> rslist = new ArrayList<>();
            String pathName = packName.replace(".", "/");
            URL url = classLoader.getResource(pathName);
            JarURLConnection jar = (JarURLConnection) url.openConnection();
            JarFile jarFile = jar.getJarFile();
            Enumeration<JarEntry> jarEntries = jarFile.entries();
            while (jarEntries.hasMoreElements()) {
                JarEntry jarEntry = jarEntries.nextElement();
                String jarEntryName = jarEntry.getName();
                if (jarEntryName.contains(pathName) && !jarEntryName.equals(pathName + "/")) {
                    if (jarEntry.isDirectory() && includeSubpack) {
                        String clazzName = jarEntry.getName().replace("/", ".");
                        int endIndex = clazzName.lastIndexOf(".");
                        String prefix = null;
                        if (endIndex > 0) {
                            prefix = clazzName.substring(0, endIndex);
                        }
                        List<Class<?>> list = findClassJar(classLoader, prefix, true);
                        rslist.addAll(list);
                    } else if (jarEntry.getName().endsWith(".class")) {
                        try {
                            Class<?> clazz = classLoader.loadClass(jarEntry.getName().replace("/", ".").replace(".class", ""));
                            rslist.add(clazz);
                        } catch (ClassNotFoundException e) {
                            //..
                        }
                    }
                }
            }
            return rslist;
        } catch (Exception e) {
            throw ExceptionUtils.toRuntime(e);
        }
    }

    public static String serialize(Object value) {
        if (value instanceof Serializable == false) {
            return null;
        }
        if (value.getClass().getTypeName().contains("$") && Modifier.isStatic(value.getClass().getModifiers()) == false) {
            return null;
        }
        boolean canser = true;
        if (value instanceof List) {
            List<?> list = (List<?>) value;
            if (list.isEmpty() == false) {
                if (list.get(0) instanceof Serializable == false) {
                    canser = false;
                }
            }
        } else if (value instanceof Map) {
            Map<?, ?> map = (Map<?, ?>) value;
            if (map.isEmpty() == false) {
                Map.Entry<?, ?> item = map.entrySet().iterator().next();
                canser = item.getKey() instanceof Serializable &&
                        item.getValue() instanceof Serializable;
            }
        }
        if (canser == false) {
            return null;
        }
        try (ByteArrayOutputStream osm = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(osm)) {
            oos.writeObject(value);
            return ByteUtils.encode(osm.toByteArray());
        } catch (IOException e) {
            throw ExceptionUtils.toRuntime(e);
        }
    }

    public static Object deserialize(String value) {
        byte[] data = ByteUtils.decode(value);
        try (ByteArrayInputStream bis = new ByteArrayInputStream(data);
             ObjectInputStream ois = new ObjectInputStream(bis)) {
            return ois.readObject();
        } catch (Exception e) {
            throw ExceptionUtils.toRuntime(e);
        }
    }

//    public static void main(String[] args) {
//
//    }
}
